package org.budo.launcher.undertow;

import java.io.File;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.servlet.jsp.JspFactory;

import org.apache.jasper.runtime.JspFactoryImpl;
import org.budo.launcher.service.AbstractBudoLauncherService;
import org.budo.launcher.service.util.LauncherUtil;
import org.budo.support.lang.util.IoUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.slf4j.Slf4j;
import org.slf4j.Logger;

import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.server.HttpHandler;
import io.undertow.server.handlers.PathHandler;
import io.undertow.server.handlers.resource.PathResourceManager;
import io.undertow.server.handlers.resource.Resource;
import io.undertow.server.handlers.resource.ResourceManager;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;
import io.undertow.servlet.api.ServletInfo;
import io.undertow.websockets.jsr.WebSocketDeploymentInfo;

/**
 * Undertow 启动程序
 * 
 * @author lmw
 */
public class BudoUndertowLauncher extends AbstractBudoLauncherService {
    private static final String WEB_SOCKET_DEPLOYMENT_INFO = "io.undertow.websockets.jsr.WebSocketDeploymentInfo";

    private static final Logger log = Slf4j.getLogger();

    private static final String HOST = "0.0.0.0";

    private static final String PORT_KEY = "undertow.port";

    @Override
    public void run(String[] args) {
        try {
            log.info("#45 BudoUndertowLauncher starting, HOST=" + HOST + ", port=" + this.getLauncherPort(PORT_KEY));

            HttpHandler httpHandler = this.httpHandler();

            Undertow undertow = Undertow.builder() //
                    .addHttpListener(this.getLauncherPort(PORT_KEY), HOST) //
                    .setHandler(httpHandler) //
                    .build();

            log.info("#53 before start, undertow=" + undertow);
            undertow.start();

            log.info("#59 BudoUndertowLauncher started, HOST=" + HOST + ", port=" + this.getLauncherPort(PORT_KEY));
        } catch (Throwable e) {
            log.error("#56 Undertow start error", e);
            throw new RuntimeException(e);
        }
    }

    private PathHandler httpHandler() throws Throwable {
        ResourceManager resourceManager = this.resourceManager();

        String contextPath = this.getContextPath();

        DeploymentInfo deploymentInfo = Servlets.deployment() //
                .setDeploymentName("BudoUndertowLauncher") //
                .setContextPath(contextPath) //
                .setClassLoader(this.getClass().getClassLoader()) //
                .setResourceManager(resourceManager);

        this.addJspServlet(deploymentInfo);

        // initWebsocketContainer
        this.initWebsocketContainer(deploymentInfo);

        // parse web.xml
        Resource webXmlResource = resourceManager.getResource("WEB-INF/web.xml");
        File webXmlFile = webXmlResource.getFile();
        InputStream inputStream = IoUtil.toInputStream(webXmlFile);

        UndertowWebXmlConfigParser webXmlConfigParser = new UndertowWebXmlConfigParser(inputStream, deploymentInfo);
        webXmlConfigParser.parseWebXml();

        DeploymentManager deploymentManager = Servlets.defaultContainer() //
                .addDeployment(deploymentInfo);

        deploymentManager.deploy();

        HttpHandler httpHandler = deploymentManager.start();
        return Handlers.path(httpHandler);
    }

    private void addJspServlet(DeploymentInfo deploymentInfo) {
        // jsp support
        if (JspFactory.getDefaultFactory() == null) {
            JspFactory.setDefaultFactory(new JspFactoryImpl());
        }

        ServletInfo servletInfo = new ServletInfo("JspServlet", BudoJspServlet.class);
        servletInfo.addMapping("*.jsp");
        deploymentInfo.addServlet(servletInfo);
    }

    private void initWebsocketContainer(DeploymentInfo deploymentInfo) {
        if (ReflectUtil.hasClass(WEB_SOCKET_DEPLOYMENT_INFO)) { // websocket init before start
            log.warn("#68 websocket init before start, deploymentInfo=" + deploymentInfo);
            deploymentInfo.addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, new WebSocketDeploymentInfo());
        }
    }

    private ResourceManager resourceManager() throws Throwable {
        File documentRoot = LauncherUtil.getDocumentRoot();
        log.info("#98 documentRoot=" + documentRoot);

        Path path = null;
        if (null != documentRoot) {
            path = Paths.get(documentRoot.toURI()); // 本地
        }

        if (null == path) {
            File webapp = new File("webapp"); // 服务器
            if (webapp.exists() && webapp.isDirectory()) {
                path = Paths.get(webapp.toURI());

                log.info("#109 path=" + path);
            }
        }

        ResourceManager resourceManager = new PathResourceManager(path);

        log.info("#61 root=" + documentRoot + ", path=" + path + ", resourceManager=" + resourceManager);
        return resourceManager;
    }
}